/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

/* nixonFTP
 * FTP client version 0.1
 * Copyright (C) 2010 NIXON Development Corporation.
 * All rights reserved.
 * http://members.shaw.ca/nixon.com
*/

package nixonftp;

import com.jcraft.jsch.*;
import com.jcraft.jsch.ChannelSftp.LsEntry;
import java.io.BufferedInputStream;
import java.io.File;
import java.io.IOException;
import java.net.UnknownHostException;
import java.util.Date;
import java.util.HashMap;
import java.util.logging.Level;
import nixonftp.error.NXLoginException;
import nixonftp.error.NXProtocolException;
import sun.net.ftp.FtpProtocolException;


public class NXSshFtpClient implements NXSocketClient {
	JSch jsch = new JSch();
	private HashMap<String, NXObjectIndex> objectIndex = new HashMap<String, NXObjectIndex>();
	private String[] arrCredentials;
	private int port = 22;
	private ChannelSftp c;
	private boolean enableLog = false;
	private NXLog log;
	private String currDir = "/";
	private boolean stopCurrent;

	public NXSshFtpClient(NXLog log) {
		super();
		this.log = log;
		enableLog = true;
	}

	protected void addToLog(String s, int type) {
		if (enableLog && this.log != null) {
			String prefix = ""; //used for color coding
			if (type == 1) prefix = "\u3000"; //error
			else if (type == 2) prefix = "\u3003"; //completed
			else if (type == 3) prefix = "\u3004"; //info

			this.log.add(prefix + s);
		}
	}

	@Override
	public void connect(String sgInitialDir) throws NXLoginException, NXProtocolException, UnknownHostException, IOException {
		Session session;

		try {
			session = jsch.getSession(arrCredentials[1], arrCredentials[0], port);
			session.setConfig("StrictHostKeyChecking", "no");
			session.setPassword(arrCredentials[2]);
			//System.out.println(session.getServerVersion());
			session.connect();

			Channel channel=session.openChannel("sftp");
			channel.connect();
			c = (ChannelSftp)channel;
			indexDirectory(sgInitialDir);
			addToLog("SSH version: " + c.version(), 3);
		} catch (JSchException ex) {
			if (ex.getMessage().contains("Auth fail")) {
				addToLog("Failed to log in to server " + arrCredentials[0] + " with username " + arrCredentials[1] + ".", 1);
				throw new NXLoginException(ex.getMessage());
			}
		} catch (SftpException ex) {
			ex.printStackTrace();
		} catch (Exception ex) {
		}
	}

	public void setCredentials(String[] credentials) {
		arrCredentials = credentials;
	}

	public String[] getCredentials() {
		return arrCredentials;
	}

	public HashMap getObjectIndex() {
		return objectIndex;
	}

	public byte[] get(String remoteFile, String localFile, boolean preview, NXProgressUpdateRunnable callBack) throws IOException {
		long size = ((NXObjectIndex)objectIndex.get(currDir).objects.get(remoteFile)).size;
		byte[] bytes = null;
		if (preview) { bytes = new byte[(int) size]; }

		try {
			addToLog("GET " + remoteFile + ", " + localFile, 0);
			if (preview) {
				int downloaded = 0; int i = 0;
				BufferedInputStream in = new BufferedInputStream(c.get(remoteFile, new NXProgressMonitor(size, callBack)));
				while (downloaded < size
						&& (i = in.read(bytes, downloaded, bytes.length - downloaded)) >= 0 && !stopCurrent) {
					downloaded += i;
					callBack.bytes = downloaded;
					callBack.add = i;
					callBack.run();
				}
				callBack.run();
			} else {
				c.get(remoteFile, localFile, new NXProgressMonitor(size, callBack), ChannelSftp.OVERWRITE);
			}
			addToLog("Successfully downloaded " + remoteFile + ".", 2);
		} catch (SftpException ex) {
			ex.printStackTrace();
			addToLog(ex.getMessage(), 1);
		}
		return bytes;
	}

	public void cdUp() throws IOException {
		try {
			changeFolder("..");
		} catch (SftpException ex) {
			throw new java.io.IOException();
		}
	}

	public void stopCurrent() {
		stopCurrent = true;
	}

	public String getWelcome() {
		return null;
	}

	public void renameImpl(String oldFile, String newFile) throws IOException, FtpProtocolException {
		throw new UnsupportedOperationException("Not supported yet.");
	}

	private class NXProgressMonitor implements SftpProgressMonitor {
		long max;
		long count = 0;
		NXProgressUpdateRunnable callBack;
		
		public void init(int op, String src, String dest, long max) {
			this.max = max;
		}

		public NXProgressMonitor(long max, NXProgressUpdateRunnable callBack) {
			init(0, "", "", max);
			this.callBack = callBack;
			callBack.size = max;
		}

		public boolean count(long count) {
			this.count += count;
			callBack.bytes = this.count;
			callBack.add = count;
			callBack.run();
			return !callBack.cancel;
		}

		public void end() {
		}

	}

	public void put(String localFile, String remoteFile, NXProgressUpdateRunnable callBack) throws IOException {
		File f = new File(localFile);
		long size = f.length();

		try {
			addToLog("PUT " + localFile + ", " + remoteFile, 0);
			c.put(localFile, remoteFile, new NXProgressMonitor(size, callBack), ChannelSftp.OVERWRITE);
			addToLog("Successfully uploaded " + localFile + ".", 2);
		} catch (SftpException ex) {
			addToLog(ex.getMessage(), 1);
		}
	}

	public int indexDirectory(String dir) throws Exception {
		addToLog("LS " + dir, 0);
		java.util.Vector dirs = c.ls(dir);
		addToLog("Received directory listing for " + dir + ".", 2);
		LsEntry entry;
		NXObjectIndex obj;
		NXObjectIndex blankObject = new NXObjectIndex('d', "/", "", "", "", -1, -1, new Date(0));
		objectIndex.put(dir, blankObject);
		NXObjectIndex dirObj = (NXObjectIndex) objectIndex.get(dir);
		SftpATTRS attrs;
		long time;

		for (int x = 0; x < dirs.size(); x++) {
			entry = (LsEntry) dirs.get(x);
			addToLog(entry.getLongname(), 2);
			if (entry.getFilename().equals(".") || entry.getFilename().equals("..")) continue;
			attrs = entry.getAttrs();
			time = ((long) attrs.getMTime()) * 1000;
			obj = new NXObjectIndex(entry.getLongname().charAt(0), entry.getFilename(), attrs.getPermissionsString(),
					 "", "", attrs.getSize(), 0, new Date(time));
			dirObj.objects.put(entry.getFilename(), obj);
		}
		
		return dirs.size();
	}

	public void fakeDirectory(NXObjectIndex oi, String path) {
		objectIndex.put(path, oi);
	}

	public void createDirectory(String dir) throws IOException {
		try {
			addToLog("MKDIR " + dir, 0);
			c.mkdir(dir);
			addToLog("Created directory " + dir, 2);
		} catch (SftpException ex) {
			ex.printStackTrace();
		}
	}

	public String escape(String s) {
		if (s.contains(" ")) return "\"" + s + "\"";
		else return s;
	}

	public void deleteDirectory(String dir) throws IOException {
		try {
			addToLog("RMDIR " + dir, 0);
			c.rmdir(changeToFolder(dir));
			addToLog("Removed directory " + dir, 2);
		} catch (SftpException ex) {
			ex.printStackTrace();
		}
	}

	public void rename(String oldFile, String newFile) throws IOException {
		try {
			addToLog("RENAME " + oldFile + " " + newFile, 0);
			//changeToFolder(oldFile);
			c.rename(oldFile, newFile);
			addToLog("Renamed " + oldFile, 2);
		} catch (SftpException ex) {
			ex.printStackTrace();
		}
	}

	public void delete(String file) throws IOException {
		try {
			addToLog("RM " + file, 0);
			c.rm(changeToFolder(file));
			addToLog("Deleted " + file, 2);
		} catch (SftpException ex) {
			ex.printStackTrace();
		}
	}

	/**
	 * Changes to the parent folder of the object indicated by dir,
	 * then returns the file or folder name
	 */
	public String changeToFolder(String dir) throws SftpException {
		String[] parts = NXFormat.getNameFromPath(dir); //get parent directory, filename only
		String fileName = parts[1];
		String pathName = parts[0];
		if (!currDir.equals(pathName)) {
			changeFolder(pathName);
		}
		return fileName;
	}

	public void changeFolder(String dir) throws SftpException {
		//String escape = escape(dir);
		try {
			if (!currDir.equals(dir)) {
				addToLog("CD " + dir, 0);
				c.cd(dir);
				currDir = dir;
				addToLog("Changed working directory to " + dir + ".", 2);
			}
		} catch (SftpException ex) {
			addToLog("Could not change directory to " + dir, 1);
			ex.printStackTrace();
			throw new SftpException(0, "");
		}
	}

	public boolean isDirectory(String dir) {
		boolean isLinkDir = false;
		try {
			c.cd(escape(dir));
			isLinkDir = true;
		} catch (SftpException ex) {
			ex.printStackTrace();
		}
		return isLinkDir;
	}

	public void disconnect() {
		if (c != null) c.quit();
	}

	public String getCurrDir() {
		if (currDir.equals("/")) return "";
		return currDir;
	}

}
